<?php

namespace Commands;

use App\Model\Server\MasterEnum;
use Database\Connection;
use Database\DatabasesProviders;
use Database\Db;
use Database\Model;
use ReflectionClass;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;

class GiiModelCommand extends Command
{

    use GiiTrait;

    protected array $hashMaps = [];

    public array $type = [
        'int'    => ['tinyint', 'smallint', 'mediumint', 'int', 'bigint', 'timestamp'],
        'string' => ['char', 'varchar', 'tinytext', 'text', 'mediumtext', 'year', 'longtext', 'date', 'time', 'datetime'],
        'json'   => ['json'],
        'float'  => ['float', 'double', 'decimal',],
        'bool'   => ['boolean',],
    ];


    /**
     * @return void
     */
    protected function configure(): void
    {
        $this->setName('sw:model')
             ->addOption('database', null, InputOption::VALUE_REQUIRED, 'is run daemonize')
             ->addOption('table', null, InputOption::VALUE_OPTIONAL, 'is run daemonize')
             ->addOption('all-table', null, InputOption::VALUE_NONE, 'is run daemonize');

    }


    /**
     * @param InputInterface $input
     * @param OutputInterface $output
     * @return int
     */
    protected function execute(InputInterface $input, OutputInterface $output): int
    {
        $daemon = (int)$input->getOption('all-table');

        $database = \Kiri::getDi()->get(DatabasesProviders::class)->get($input->getOption('database'));

        $namespace = $input->getOption('database');
        if ($daemon) {
            $tables = Db::connect($database)->fetchAll('show tables');
            foreach ($tables as $table) {
                $t   = current($table);
                $sql = Db::connect($database)->fetch('show create table ' . $t)['Create Table'];
                $this->putFile(Db::desc($t, $database), $namespace, $database, $t, $sql);
            }
        } else {
            $t   = $input->getOption('table');
            $sql = Db::connect($database)->fetch('show create table ' . $t)['Create Table'];
            $this->putFile(Db::desc($t, $database), $namespace, $database, $t, $sql);
        }
        return 0;
    }


    /**
     * @param array $data
     * @param string $namespace
     * @param Connection $database
     * @param string $table
     * @return void
     */
    protected function putFile(array $data, string $namespace, Connection $database, string $table, string $sql): void
    {
        $toArray    = [];
        $primaryKey = '';

        ini_set('memory_limit', '2G');

        $condition = ['PRI', 'UNI'];

        $hasJsonValidate = false;
        $methods         = [];

        $types      = [];
        $rules      = [];
        $properties = [];
        foreach ($data as $datum) {
            $fieldType = '\'string\'';
            if ($datum['Extra'] != 'auto_increment') {
                if (!empty($primaryKey) && in_array($datum['Key'], $condition)) {
                    $primaryKey              = $datum['Field'];
                    $rules['\'required\''][] = $datum['Field'];
                }
                if ($datum['Key'] == 'MUL' || $datum['Key'] == 'NUI' || $datum['Key'] == 'PRI') {
                    $rules['\'required\''][] = $datum['Field'];
                }
                if ($datum['Null'] == 'NO') {
                    $rules['\'not null\''][] = $datum['Field'];
                }
                if (str_contains($datum['Type'], ' ')) {
                    if (str_contains($datum['Type'], 'unsigned')) {
                        $rules['\'min\' => 0'][] = $datum['Field'];
                    }
                    $datum['Type'] = explode(' ', $datum['Type'])[0];
                }
                if ($datum['Type'] == 'json') {
                    $hasJsonValidate = true;
                    $methods[]       = $this->_methods($datum);
                }

                if (str_starts_with($datum['Type'], 'enum')) {
                    $trim                                                           = str_replace('enum(', '', $datum['Type']);
                    $trim                                                           = str_replace(')', '', $trim);
                    $rules['\'enum\' => [' . str_replace(',', ', ', $trim) . ']'][] = $datum['Field'];
                } else if (str_starts_with($datum['Type'], 'set')) {
                    $trim                                                           = str_replace('set(', '', $datum['Type']);
                    $trim                                                           = str_replace(')', '', $trim);
                    $rules['\'enum\' => [' . str_replace(',', ', ', $trim) . ']'][] = $datum['Field'];
                } else {
                    $values = explode('(', $datum['Type']);
                    if (isset($values[1])) {
                        $values[1] = rtrim($values[1], ')');
                        if (str_contains($values[1], ',')) {
                            $values[1] = explode(',', $values[1]);
                        }
                    }
                    if (isset($values[1])) {
                        if (is_array($values[1])) {
                            $rules['\'maxLength\' => ' . $values[1][0] . ', \'round\' => ' . $values[1][1]][] = $datum['Field'];
                        } else {
                            $rules['\'maxLength\' => ' . $values[1]][] = $datum['Field'];
                        }
                    } else {
                        if ($datum['Type'] == 'int') {
                            $rules['\'maxLength\' => 10'][] = $datum['Field'];
                        }
                        if ($datum['Type'] == 'bigint') {
                            $rules['\'maxLength\' => 20'][] = $datum['Field'];
                        }
                        if ($datum['Type'] == 'smallint') {
                            $rules['\'maxLength\' => 5'][] = $datum['Field'];
                        }
                        if ($datum['Type'] == 'mediumint') {
                            $rules['\'maxLength\' => 8'][] = $datum['Field'];
                        }
                        if ($datum['Type'] == 'tinyint') {
                            $rules['\'maxLength\' => 3'][] = $datum['Field'];
                        }
                    }
                    foreach ($this->type as $key => $type) {
                        if (in_array($values[0], $type)) {
                            $fieldType = '\'' . $key . '\'';
                            break;
                        }
                    }
                }

                $types[$fieldType][] = $datum['Field'];
                if ($fieldType == '\'json\'') {
                    $types['\'string\''][] = $datum['Field'];

                    $properties[] = ' * @property array $' . $datum['Field'];
                } else {
                    $properties[] = ' * @property ' . trim($fieldType, '\'') . ' $' . $datum['Field'];
                }
            } else {
                $primaryKey = $datum['Field'];
                if (str_contains($datum['Type'], ' ')) {
                    $properties[] = ' * @property ' . explode(' ', $datum['Type'])[0] . ' $' . $datum['Field'];
                } else {
                    $str = ' * @property ' . $datum['Type'] . ' $' . $datum['Field'];
                    if ($datum['Type'] == 'bigint') {
                        $str = ' * @property int $' . $datum['Field'];
                    }
                    $properties[] = $str;
                }
            }
        }

        $json = [];
        foreach ($types as $rule => $type) {
            if (!isset($json['[\'' . implode('\', \'', $type) . '\']'])) {
                $json['[\'' . implode('\', \'', $type) . '\']'] = [];
            }
            $json['[\'' . implode('\', \'', $type) . '\']'][] = $rule;
        }

        foreach ($rules as $rule => $type) {
            if (!isset($json['[\'' . implode('\', \'', $type) . '\']'])) {
                $json['[\'' . implode('\', \'', $type) . '\']'] = [];
            }
            $json['[\'' . implode('\', \'', $type) . '\']'][] = $rule;
        }
        foreach ($json as $field => $value) {
            if (in_array('\'required\'', $value)) {
                array_unshift($toArray, '
            [' . $field . ', ' . implode(', ', $value) . '],');
            } else {
                $toArray[] = '
            [' . $field . ', ' . implode(', ', $value) . '],';
            }
        }
        if ($hasJsonValidate) {
            $hasJsonValidate = $this->_jsonValidate();
            $hasJsonValidate .= implode($methods);
        }

        $dbName = $this->clearXiaHuaXian($namespace);
        $table  = str_replace($database->tablePrefix, '', $table);
        if (str_contains($table, '-')) {
            $table = str_replace('-', '_', $table);
        }
        $class  = $this->clearXiaHuaXian($table, '');
        $prefix = $dbName == 'Db' ? '' : '\\' . ucfirst($dbName);
        $html   = $this->_html($sql, $prefix, $class, $properties, $primaryKey, $table, $namespace, $toArray, $hasJsonValidate);
        $dir    = APP_PATH . 'app/Model';
        if ($namespace != 'db') {
            $dir = APP_PATH . 'app/Model/' . ucfirst($dbName);
        }
        if (!is_dir($dir)) {
            mkdir($dir, 0777, true);
        }

        file_put_contents($dir . '/' . $class . '.php', $html);
    }

    /**
     * @return string
     */
    protected function _jsonValidate(): string
    {
        return '
    /**
     * @param string $value
     * @return bool
     */
    public function json(string $value): bool
    {
        return \json_validator($value);
    }

';
    }


    /**
     * @param $sql
     * @param $prefix
     * @param $class
     * @param $properties
     * @param $primaryKey
     * @param $table
     * @param $namespace
     * @param $toArray
     * @param $hasJsonValidate
     * @return string
     */
    protected function _html($sql, $prefix, $class, $propertiesDoc, $primaryKey, $table, $namespace, $toArray, $hasJsonValidate): string
    {
        if (!class_exists('App\Model' . $prefix . '\\' . $class)) {
            return $this->newFileContent($sql, $prefix, $class, $propertiesDoc, $primaryKey, $table, $namespace, $toArray, $hasJsonValidate);
        }
        $reflection   = new ReflectionClass('App\Model' . $prefix . '\\' . $class);
        $constantHtml = [];
        $old          = $reflection->getConstants();
        $length       = $this->maxStringLength($old);
        foreach ($old as $key => $constant) {
            $constantHtml[] = '
    const ' . $key . str_pad('', $length - mb_strlen($key), ' ') . ' = ' . $constant . ';';
        }

        $array = ['Database\ModelInterface', 'ArrayAccess', 'Arrayable', 'Kiri\Abstracts\Configure'];

        $interfaces = $this->getImports($reflection, []);
        [$interfaces, $names] = $this->getInterfaces($reflection, $interfaces, $array);

        [$interfaces, $traits] = $this->getTraits($reflection, $interfaces);


        [$interfaces, $oldMethods] = $this->getOldMethods($reflection, $interfaces, ['rules']);

        $hasJsonValidate = trim(implode(PHP_EOL . PHP_EOL, array_filter($oldMethods)));

        [$interfaces, $properties] = $this->resetProperty($reflection, $interfaces);

        return '<?php

declare(strict_types=1);

namespace App\Model' . $prefix . ';


' . implode(PHP_EOL, array_unique($interfaces)) . '

// ' . implode(PHP_EOL . '// ', explode(PHP_EOL, $sql)) . '

/**
 * Class  App\Form' . $prefix . '\\' . $class . '
' . implode(PHP_EOL, $propertiesDoc) . '
 */
class ' . $class . ' extends Model' . (empty($names) ? '' : ' implements ' . implode(',', $names)) . '
{
' . implode(PHP_EOL, $traits) . implode($constantHtml) . '

    /** @var string */
    protected string $primary = \'' . $primaryKey . '\';


    /**
     * @var string
     * @inheritdoc
     */
    protected string $table = \'' . $table . '\';


    /**
     * @var string
     */
    protected string $connection = \'' . $namespace . '\';
' . implode($properties) . '
    /**
     * @return array
     */
    public function rules(): array
    {
        return [' . implode($toArray) . '
        ];
    }

    ' . $hasJsonValidate . '
}';
    }


    /**
     * @param $sql
     * @param $prefix
     * @param $class
     * @param $properties
     * @param $primaryKey
     * @param $table
     * @param $namespace
     * @param $toArray
     * @param $hasJsonValidate
     * @return string
     */
    protected function newFileContent($sql, $prefix, $class, $properties, $primaryKey, $table, $namespace, $toArray, $hasJsonValidate): string
    {
        return '<?php

declare(strict_types=1);

namespace App\Model' . $prefix . ';


use Database\Model;


/**
 * Class  App\Form' . $prefix . '\\' . $class . '
' . implode(PHP_EOL, $properties) . '
 *
 *
 * @sql
 * ' . implode(PHP_EOL . ' * ', explode(PHP_EOL, $sql)) . ';
 */
class ' . $class . ' extends Model
{


    /** @var string */
    protected string $primary = \'' . $primaryKey . '\';


    /**
     * @var string
     * @inheritdoc
     */
    protected string $table = \'' . $table . '\';


    /**
     * @var string
     */
    protected string $connection = \'' . $namespace . '\';


    /**
     * @return array
     */
    public function rules(): array
    {
        return [' . implode($toArray) . '
        ];
    }

' . $hasJsonValidate . '
}';
    }


    /**
     * @param array $datum
     * @return string
     */
    protected function _methods(array $datum): string
    {
        return implode(PHP_EOL . PHP_EOL, [
                '
    /**
     * @param array|null $value
     * @return bool|string
     */
    public function set' . ucfirst($datum['Field']) . 'Attribute(?array $value): bool|string
    {
        return \json_encode($value, JSON_UNESCAPED_UNICODE);
    }',
                '
    /**
     * @param string|null $value
     * @return array|null|bool
     */
    public function get' . ucfirst($datum['Field']) . 'Attribute(?string $value): array|null|bool
    {
        return \json_decode($value, true);
    }'
            ]) . PHP_EOL . PHP_EOL;
    }
}